/// <reference path="./Maths.ts" />
/// <reference path="./Pool.ts" />

namespace apeng {
    let _x: number = 0.0
    let _y: number = 0.0
    let _z: number = 0.0

    export interface IVector3 {
        x: number
        y: number
        z: number
    }
    export type TVector3Set = number | number[] | IVector3

    export class Vector3 implements IVector3 {

        public static readonly pool = new PoolOnce(1000, () => new Vector3(), (data) => { data.set() })

        /**零向量,禁止修改*/
        public static readonly ZERO = new Vector3(0.0, 0.0)

        /**
         *  排除浮点数误差的向量近似等价判断
         */
        public static equals(a: IVector3, b: IVector3, epsilon = 0.01) {
            return (
                Math.abs(a.x - b.x)
                <= epsilon * Math.max(1.0, Math.abs(a.x), Math.abs(b.x))
                && Math.abs(a.y - b.y)
                <= epsilon * Math.max(1.0, Math.abs(a.y), Math.abs(b.y))
                && Math.abs(a.z - b.z)
                <= epsilon * Math.max(1.0, Math.abs(a.z), Math.abs(b.z))
            )
        }

        /** 逐元素向量加法  */
        public static add(out: IVector3, a: IVector3, b: IVector3) {
            out.x = a.x + b.x
            out.y = a.y + b.y
            out.z = a.z + b.z
            return out
        }
        /**逐元素向量减法 */
        public static sub(out: IVector3, a: IVector3, b: IVector3) {
            out.x = a.x - b.x
            out.y = a.y - b.y
            out.z = a.z - b.z
            return out
        }

        /**绕X轴旋转向量指定角度度 */
        public static rotateX(out: IVector3, rotate: IVector3, orgin: IVector3, angle: number) {
            const x = rotate.x - orgin.x
            const y = rotate.y - orgin.y
            const z = rotate.z - orgin.z

            const radian = Maths.toRadian(angle)
            const cos = Math.cos(radian)
            const sin = Math.sin(radian)
            const rx = x
            const ry = y * cos - z * sin
            const rz = y * sin + z * cos

            out.x = rx + orgin.x
            out.y = ry + orgin.y
            out.z = rz + orgin.z

            return out
        }

        /**绕Y轴旋转向量指定角度度 */
        public static rotateY(out: IVector3, rotate: IVector3, orgin: IVector3, angle: number) {
            const x = rotate.x - orgin.x
            const y = rotate.y - orgin.y
            const z = rotate.z - orgin.z

            const radian = Maths.toRadian(angle)
            const cos = Math.cos(radian)
            const sin = Math.sin(radian)
            const rx = z * sin + x * cos
            const ry = y
            const rz = z * cos - x * sin

            out.x = rx + orgin.x
            out.y = ry + orgin.y
            out.z = rz + orgin.z
            return out
        }

        /**绕Z轴旋转向量指定角度度 */
        public static rotateZ(out: IVector3, rotate: IVector3, orgin: IVector3, angle: number) {
            const x = rotate.x - orgin.x
            const y = rotate.y - orgin.y
            const z = rotate.z - orgin.z

            const radian = Maths.toRadian(angle)
            const cos = Math.cos(radian)
            const sin = Math.sin(radian)
            const rx = x * cos - y * sin
            const ry = x * sin + y * cos
            const rz = z;

            out.x = rx + orgin.x
            out.y = ry + orgin.y
            out.z = rz + orgin.z

            return out;
        }



        /**
        * 缩放当前向量。如果你想结果保存到另一个向量，可使用 mul() 代替。
        * @method mulSelf
        * @param {number} num
        * @return {Vec2} returns this
        * @chainable
        * @example
        * var v = _cc_.v2(10, 10)
        * v.mulSelf(5)// return Vec2 {x: 50, y: 50}
        */
        public static mul(out: IVector3, value: IVector3, num: number | IVector3) {
            if (typeof num == "number") {
                out.x = value.x * num
                out.y = value.y * num
                out.z = value.z * num
            }
            else {
                out.x = value.x * num.x
                out.y = value.y * num.y
                out.z = value.z * num.z
            }
            return out
        }
        public static cloneTo(self: IVector3, out: Vector3): Vector3 {
            out.x = self.x
            out.y = self.y
            out.z = self.z
            return out
        }

        public static set(self: IVector3, x?: TVector3Set, y?: number, z?: number): IVector3 {
            if (Array.isArray(x)) {
                self.x = x[0]
                self.y = x[1]
                self.z = x[2] || 0
            }
            else if (x && typeof x === "object") {
                self.x = x.x
                self.y = x.y
                self.z = x.z
            } else {
                self.x = (x || 0) as number
                self.y = y || 0
                self.z = z || 0
            }
            return self as IVector3
        }

        /**
         * 转换为方便阅读的字符串。
         * @method toString
         * @return {string}
         */
        public static toString(value: IVector3): string {
            return "(" + value.x.toFixed(2) + ", " + value.y.toFixed(2) + ", " + value.z.toFixed(2) + ")"
        }



        /**
         * 求向量长度
         */
        public static len(value: IVector3): number {
            return Math.sqrt(Vector3.lengthSqr(value))
        }

        /**
         * 求向量长度平方
         */
        public static lengthSqr(value: IVector3): number {
            return value.x * value.x + value.y * value.y + value.z * value.z
        }

        /**
         * 将当前向量归一化
         */
        public static normalize(out: IVector3, value: IVector3) {
            const x = value.x
            const y = value.y
            const z = value.z

            let len = x * x + y * y + z * z;
            if (len > 0) {
                len = 1 / Math.sqrt(len)
                out.x = x * len
                out.y = y * len
                out.z = z * len
            }
            return out
        }

        /**
         * 找到多边形的中心点
         * @param polygon 
         */
        public static polygonByCenterPoint(out: IVector3, polygon: IVector3[]): IVector3 {
            let minX = polygon[0].x
            let maxX = polygon[0].x
            let minY = polygon[0].y
            let maxY = polygon[0].y
            let minZ = polygon[0].z
            let maxZ = polygon[0].z
            for (let value of polygon) {
                if (minX > value.x)
                    minX = value.x
                if (maxX < value.x)
                    maxX = value.x
                if (minY > value.y)
                    minY = value.y
                if (maxY < value.y)
                    maxY = value.y
                if (minZ > value.z)
                    minZ = value.z
                if (maxZ < value.z)
                    maxZ = value.z
            }
            out.x = minX + Math.abs(maxX - minX) / 2
            out.y = minY + Math.abs(maxY - minY) / 2
            out.z = minZ + Math.abs(maxZ - minZ) / 2
            return out
        }

        /**
         * 两个三维向量距离。 不开方
         * @param	value1 向量1。
         * @param	value2 向量2。
         * @return	距离。
         */
        public static distanceSqr(value1: IVector3, value2: IVector3): number {
            v3T.x = value1.x - value2.x
            v3T.y = value1.y - value2.y
            v3T.z = value1.z - value2.z
            return (v3T.x * v3T.x) + (v3T.y * v3T.y) + (v3T.z * v3T.z)
        }

        /**
       * 两个三维向量距离。
       * @param	value1 向量1。
       * @param	value2 向量2。
       * @return	距离。
       */
        public static distance(value1: IVector3, value2: IVector3): number {
            return Math.sqrt(this.distanceSqr(value1, value2))
        }


        /**
         * @zh 根据指定的插值比率，从当前向量到目标向量之间做插值。
         * @param to 目标向量。
         * @param ratio 插值比率，范围为 [0,1]。
         */
        public static lerp(out: IVector3, from: IVector3, to: IVector3, ratio: number | IVector3) {
            _x = from.x
            _y = from.y
            _z = from.z
            if (typeof ratio === "number")
                v3T.set(ratio, ratio, ratio)
            else
                v3T.set(ratio)
            out.x = _x + v3T.x * (to.x - _x)
            out.y = _y + v3T.y * (to.y - _y)
            out.z = _z + v3T.z * (to.z - _z)
            return out
        }

        public x: number = 0
        public y: number = 0
        public z: number = 0

        constructor(x?: TVector3Set, y?: number, z?: number) {
            this.set(x as number, y, z)
        }

        public set(x?: TVector3Set, y?: number, z?: number): Vector3 { return Vector3.set(this, x, y, z) as Vector3 }
        public equals(b: IVector3, epsilon = 0.01) { return Vector3.equals(this, b, epsilon) }
        public sub(out: IVector3, other: IVector3) { return Vector3.sub(out, this, other) as Vector3 }
        public subSelf(other: IVector3) { return Vector3.sub(this, this, other) as Vector3 }
        public add(out: IVector3, other: IVector3) { return Vector3.add(out, this, other) as Vector3 }
        public addSelf(other: IVector3) { return Vector3.add(this, this, other) as Vector3 }
        public clone(): Vector3 { return new Vector3(this) }
        public len() { return Vector3.len(this) }
        public toString() { return Vector3.toString(this) }
        public lengthSqr() { return Vector3.lengthSqr(this) }
        public mul(out: IVector3, num: number) { return Vector3.mul(out, this, num) as Vector3 }
        public mulSelf(num: number): Vector3 { return Vector3.mul(this, this, num) as Vector3 }
        public rotateX(orgin: IVector3, angle: number): Vector3 { return Vector3.rotateX(this, this, orgin, angle) as Vector3 }
        public rotateY(orgin: IVector3, angle: number): Vector3 { return Vector3.rotateY(this, this, orgin, angle) as Vector3 }
        public rotateZ(orgin: IVector3, angle: number): Vector3 { return Vector3.rotateZ(this, this, orgin, angle) as Vector3 }
        public normalize(out: IVector3): Vector3 { return Vector3.normalize(out, this) as Vector3 }
        public normalizeSelf(): Vector3 { return Vector3.normalize(this, this) as Vector3 }

    }

    const v3T = new Vector3()

}

